import lodash from 'lodash'
import { JobAction, JobRecordModel } from './JobRecordModel'
import Decimal from 'decimal.js'

/**
 * 工资段
 *
 * 工人的工资可能会改变，在累加计算之前，
 * 先分成许多段
 */
class SalaryFragment {
  private start: Date
  private end: Date | null
  private pauseRecords: JobRecordModel[]

  /**
   * # 工作段
   * 区别于完整的工作记录，工作段内的工资不变，排班不变
   * @param start 开始日期
   * @param end 结束日期
   * @param leftDays 请假天数
   * @param salaryMonthly 此段内的月薪
   * @param schedule 当前排期
   */
  constructor(
    start: Date,
    end: Date | null,
    pauseRecords: JobRecordModel[],
    private salaryMonthly: number,
    private schedule: string,
  ) {
    this.start = new Date(start)
    this.end = end ? new Date(end) : end
    this.pauseRecords = lodash.cloneDeep(pauseRecords)

    this.start.setHours(0)
    this.start.setMinutes(0)
    this.start.setSeconds(0)
    this.start.setMilliseconds(0)

    if (this.end) {
      this.end.setHours(0)
      this.end.setMinutes(0)
      this.end.setSeconds(0)
      this.end.setMilliseconds(0)
    }

    this.pauseRecords.forEach(r => {
      r.date.setHours(0)
      r.date.setMinutes(0)
      r.date.setSeconds(0)
      r.date.setMilliseconds(0)
    })

    // console.log('pauseRecords -----');
    // console.log(this.pauseRecords);
    
    
  }

  /** 工作时间（以天计 */
  workDays() {

    // 如果是封闭段（有开始有结束）
    if (this.end) {
      const duration = Math.round(
        (this.end.valueOf() - this.start.valueOf()) / (86400 * 1000),
      )

      // 封闭段中如果包含请假，就一定是生效的，就不需要判断
      const leftDays = this.pauseRecords.map(x => Number.parseFloat(x.actionParams[0])).reduce((y, x) => y + x, 0)
  
      // 工作时间包含离职当天
      const d = (new Decimal(duration)).minus(leftDays).plus(1)
      return d.toNumber()
    }
    // 开放段要根据当前schedule精确的计算今天的工资给没给
    else {
      console.log('================here', this);
      
      // 从入职时间算，除一天的时间，商为工作天数（已经过去的），余数如果超过了工作时，则当天也工作完了。
      const _start = new Date(this.start)
      if (this.schedule === '白班') {
        _start.setHours(8)
      }
      else if (this.schedule === '夜班') {
        _start.setHours(20)
      }
      else {
        // 如果是其他则不修改
      }

      // 从入职时间开始算过了多久
      const dur = ((new Date()).valueOf() - _start.valueOf()) / (86400 * 1000)
      
      // 过去的整天
      const durInt = Math.floor(dur)

      // 如果是白班夜班，工作12小时，要考虑小数点部分
      const durFlo = Math.floor((dur * 10) % 10)

      let duration: number
      if (['白班', '夜班'].includes(this.schedule) && durFlo >= 5) {
        duration = durInt + 1
      }
      else {
        duration = durInt
      }

      // console.log('================duration', duration);
      

      // console.log(`dur --- ${dur}`);
      // console.log(`durInt --- ${durInt}`);
      // console.log(`durFlo --- ${durFlo}`);
      // console.log(`open duration --- ${duration}`);

      // 请假记录都被分割成了单天的，这样方便判断
      let leftDays = 0
      this.pauseRecords.forEach(record => {
        // 当前时间
        const now = Date.now()
        // 今天0点
        const today0h = (() => {
          const t = new Date()
          t.setHours(0)
          t.setMinutes(0)
          t.setSeconds(0)
          t.setMilliseconds(0)
          return t.valueOf()
        })();
        // 昨天0点
        const yesterday0h = (() => {
          const t = new Date()
          t.setHours(0)
          t.setMinutes(0)
          t.setSeconds(0)
          t.setMilliseconds(0)
          return t.valueOf() - 86400 * 1000
        })();

        // 请假日期在今天之后，工作未发生，对工作天数不影响
        if (record.date.valueOf() > today0h) {
          return
        }

        // console.log('############### 1');
        
        // 请假日期在昨天之前，所有人的工资都结算了，直接减去
        if (record.date.valueOf() < yesterday0h) {
          // console.log(`leftDays += Number.parseFloat(${record.actionParams[0]})`);
          
          leftDays += Number.parseFloat(record.actionParams[0])
          // console.log(`leftDays = ${leftDays}`);
          return
        }

        // console.log('############### 2');

        // 昨天请假了
        // 白班，其他的都直接扣，夜班要等到8点结算后才扣一天
        if (record.date.valueOf() === yesterday0h) {
          if (this.schedule === '夜班' && now - today0h < 8 * 3600 * 1000) {
            return
          }
          leftDays += Number.parseFloat(record.actionParams[0])
          return
        }

        // console.log('############### 3');
        // console.log(this.schedule);
        // console.log(record.date.valueOf());
        // console.log(today0h);
        // console.log((now - today0h)/3600000);
        

        // 请假日期发生在今天
        // 夜班，其他都不扣，因为完成发生在次日
        // 白班20点前不扣，到20点结算了扣一天
        if (record.date.valueOf() === today0h) {
          if (
            this.schedule === '夜班'
            || this.schedule === '其他'
            || (this.schedule === '白班' && now - today0h < 20 * 3600 * 1000)
          ) {
            return
          }
          
          
          leftDays += Number.parseFloat(record.actionParams[0])
          return
        }
      })

      // console.log('############### 4');
      
      console.log(`leftDays --- ${leftDays}`);
      const d = (new Decimal(duration)).minus(leftDays)

      console.log('================== d', d.toNumber());
      

      if (d.lessThan(0)) {
        return 0
      }

      return d.toNumber()
    }
  }

  /** 总薪资 */
  salaryTotal() {
    return Math.floor(this.workDays() * this.salaryMonthly / 30)
  }

  /**
   * 修改结束时间
   * @param t 
   */
  updateEnd(t: Date) {
    this.end = new Date(t)
    this.end.setHours(0)
    this.end.setMinutes(0)
    this.end.setSeconds(0)
    this.end.setMilliseconds(0)
  }
}

/**
 * # 工资计算模型
 * 计算从入职 - 到结清的所有工资相关的项目，例如
 * - 工作天数
 * - 薪资总计
 * - 已发/未发工资等
 */
export class SalaryModel {

  // 被验证过，排序过的工作记录
  private records: JobRecordModel[]

  // 导出工作记录
  getRecords(): JobRecordModel[] {
    return lodash.cloneDeep(this.records)
  }

  /**
   * @param records 某个工人的所有工作记录
   */
  constructor(records: JobRecordModel[]) {
    // 检验记录中工人是否唯一
    const _idArr = records.map(x => x.worker?.id)
    const _idSet = new Set(_idArr)
    if (_idSet.size > 1) {
      console.error('new SalaryModel: 工作记录归属人不唯一')
    }
    
    // 拷贝记录，进行排序
    this.records = lodash.cloneDeep(records)
    this.records.sort((a, b) => a.date.valueOf() - b.date.valueOf())
  }

  /** 工作记录所属工人 */
  worker() {
    return this.records[0].worker!
  }

  /** 最近的就职记录，用来计算：
   *  - 当前工资
   */
  private _latestStart() {
    const filtered = this.records.filter(x => x.action === JobAction.Start)
    return lodash.last(filtered)
  }

  /**
   * 当前月薪
   */
  currentMonthlySalary() {
    const ls = this._latestStart()
    if (!ls) {
      return 0
    }
    const param = ls.actionParams[0]
    return Number.parseInt(param)
  }

  /**
   * [弃用] 工作记录分段功能已经实现，不再需要在一个工作段内判断结清与否
   * 未结清工作记录
   *
   * 执行结清动作，将会使之前的记录不再计入工作天数。
   * 用于方便将工资分段统计
   * 
   */
  // unSettledRecords() {
  //   const filtered = this.records.filter(x => x.action === JobAction.Settlement)
  //   const lastSettlement = lodash.last(filtered)
  //   if (!lastSettlement) {
  //     return this.records
  //   }
  //   else {
  //     const index = this.records.findIndex(x => x === lastSettlement)
  //     if (index === this.records.length - 1) {
  //       return []
  //     }
  //     else {
  //       return this.records.slice(index + 1)
  //     }
  //   }
  // }

  /**
   * 
   */
  unSettledRecords() {
    const latest = lodash.last(SalaryModel.divideHistoryGroups(this.records))
    return latest?.getRecords()!
  }

  /**
   * 将工作记录进行分组
   *
   * 分组的依据仅仅是入职和离职的记录，以及排期更改。
   * 至于开工资被分到哪一组并不重要。
   * 因为工作片模型是用来计算应发工资的。
   * 已发工资和未发工资的概念存在于工作记录组，不是工作记录片段
   */
  fragmentGroups() {
    // 如果工作记录尚不存在，或者工作记录的第一条不是入职
    // 则说明发生了某种错误，返回空组
    const firstRec = this.records[0]
    if (!firstRec || firstRec.action !== JobAction.Start) {
      return []
    }

    const groups = [] as JobRecordModel[][]
    let index = -1
    for (const record of this.records) {
      // 遇到入职（更改工资）/更改排期时切换到下一组
      if (
        record.action === JobAction.Start
        || record.action === JobAction.Schedule
      ) {
        index += 1
        groups[index] = []
      }
      groups[index].push(record)
    }

    return groups
  }

  private _fragments() {
    const res = []

    /**
     * # 当前月薪
     * 分组中的第一条可能是改排期，不是改薪水
     * 这时候此段月薪沿用上一段的
     */
    let cms = 0

    /**
     * 上一个片段组，保留引用。
     * 如果下一个片段是更改排期，会自动把上一个段的时间设为改排期的前一天
     */
    let lastFrag = null as null | SalaryFragment

    // 将分好组的工作记录转化成fragment对象，分段求工资
    for (const group of this.fragmentGroups()) {

      // 验证：段的第一个事件是指定月薪或更改排期
      if (group[0].action !== JobAction.Start && group[0].action !== JobAction.Schedule) {
        throw '工作记录片段检验失败'
      }

      // 如果是入职（指定月薪），则更新cms。如果不是，就沿用上一段的
      if (group[0].action === JobAction.Start) {
        cms = Number.parseFloat(group[0].actionParams[0])
      }
      // 如果是改排期，要补充一下上一段的结束时间。
      if (group[0].action === JobAction.Schedule) {
        lastFrag?.updateEnd(new Date(group[0].date.valueOf() - 86400000))
      }

      /** 当前排期 */
      let cs: string
      if (group[0].action === JobAction.Start) {
        cs = group[0].actionParams[1]
      }
      else {
        cs = group[0].actionParams[0]
      }

      // 第一个事件(入职/改薪/排期)的日期作为段的开始日期
      const start = group[0].date

      // 离职不一定是最后一个事件
      const _endRec = group.find(x => x.action === JobAction.End)

      // 不一定离职了，有可能是现行记录
      // const end = _endRec ? _endRec.date : new Date(Date.now() - 86400000)
      const end = _endRec ? _endRec.date : null

      // 请假记录分割，大于1天的分割成多天
      const _pausedRecords = group.filter(x => x.action === JobAction.Pause)
      const pauseRecords = [] as JobRecordModel[]
      _pausedRecords.forEach(record => {
        const day = record.actionParams[0]

        // 如果请假小于1天就直接放入
        if (day <= 1) {
          pauseRecords.push(record)
        }
        // 如果请假天数大于1，就分成多天
        else {
          const floatPart = day % 1

          // 有小数
          if (floatPart != 0) {
            const r = lodash.cloneDeep(record)
            r.actionParams[0] = floatPart
            for (let i=0;i<day-floatPart;i++) {
              const r = lodash.cloneDeep(record)
              r.date = new Date(record.date.valueOf() + (i+1) * 86400000)
              r.actionParams[0] = 1
              pauseRecords.push(r)
            }
          }
          // 无小数
          else {
            for (let i=0;i<day;i++) {
              const r = lodash.cloneDeep(record)
              r.date = new Date(record.date.valueOf() + i * 86400000)
              r.actionParams[0] = 1
              pauseRecords.push(r)
            }
          }
        }
      })

      // const leftDays = pauseRecords.map(x => Number.parseFloat(x.actionParams[0])).reduce((y, x) => y + x, 0)
      const fragment = new SalaryFragment(start, end, pauseRecords, cms, cs)
      // sum += fragment.salaryTotal()
      res.push(fragment)
      lastFrag = fragment
      // console.log(fragment.workDays());
    }
    return res
  }

  /** 所有工作日 */
  totalWorkDays() {
    const a = this._fragments().map(x => x.workDays())
    // console.log('totalWorkDays--');
    // console.log(a)
    const d = new Decimal(a.reduce((y, x) => y + x, 0))
    // console.log('=============================');
    
    // console.log('a', a);
    // console.log('--------------');// asssadasd
    
    return d.toNumber()
  }

  /** 所有薪水（不含奖金处罚 */
  totalSalary() {
    // console.log('totalSalary--');
    // console.log(this._fragments().map(x => x.salaryTotal()))
    return this._fragments().map(x => x.salaryTotal()).reduce((y, x) => y + x, 0)
  }

  /** 所有奖金 */
  totalBonus() {
    return this.records.filter(
      x => x.action === JobAction.Bonus,
    ).map(x => Number.parseInt(x.actionParams[0])).reduce((y, x) => y + x, 0)
  }

  /** 所有处罚 */
  totalBlame() {
    return this.records.filter(
      x => x.action === JobAction.Blame,
    ).map(x => Number.parseInt(x.actionParams[0])).reduce((y, x) => y + x, 0)
  }

  /** 总计应发 = 薪水 + 奖金 - 罚金 */
  totalShouldSent() {
    return this.totalSalary() - this.totalBlame() + this.totalBonus()
  }

  /** 所有支取 */
  totalAdvance() {
    return this.records.filter(
      x => x.action === JobAction.Advance,
    ).map(x => Number.parseInt(x.actionParams[0])).reduce((y, x) => y + x, 0)
  }

  /** 剩余未发 */
  totalShouldSentRemaining() {
    return this.totalShouldSent() - this.totalAdvance()
  }

  /**
   * 将历史工作记录分组
   * 每个工作记录组都包括从入职到结清（计算方式是仅以结清分割）
   */
  static divideHistoryGroups(historyRecords: JobRecordModel[]): SalaryModel[] {

    // 整理顺序
    const _group = (new SalaryModel(historyRecords)).getRecords()

    const groups = [] as JobRecordModel[][]
    let curr_group = [] as JobRecordModel[]
    
    _group.forEach(r => {
      curr_group.push(r)
      if (r.action === JobAction.Settlement) {
        groups.push(curr_group)
        curr_group = []
      }
    })
    // 尾组：即正在进行的工作组
    groups.push(curr_group)
    // console.log(groups);
    
    // const filtered = this.records.filter(x => x.action === JobAction.Settlement)
    // const lastSettlement = lodash.last(filtered)
    // if (!lastSettlement) {
    //   return this.records
    // }
    // else {
    //   const index = this.records.findIndex(x => x === lastSettlement)
    //   if (index === this.records.length - 1) {
    //     return []
    //   }
    //   else {
    //     return this.records.slice(index + 1)
    //   }
    // }

    return groups.map(x => new SalaryModel(x))
  }

  /**
   * 将历史工作记录分组
   * 每个工作记录组都包括从入职到结清（计算方式是仅以结清分割）
   */
  static latest(historyRecords: JobRecordModel[]): SalaryModel {
    const groups = this.divideHistoryGroups(historyRecords)
    return lodash.last(groups)!
  }
}
